package network_programming.np1_socket_and_serversocket;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.*;

public class SocketDemo {
    /**
     * 默认情况下，socket构造方法会一直等待下去，直到连接成功，或出现异常
     * 设置等待连接的时间时需要使用不带参数的构造方法
     * */
    public void test_1() {  // 设置等待建立连接的超时时间
        try (Socket socket = new Socket()) {
            SocketAddress remoteAddress = new InetSocketAddress("localhost", 8000);
                        // 服务器地址
            socket.connect(remoteAddress, 3000);    // 连接并设置等待连接的超时时间为3秒
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * Socket的构造方法
     * Socket()
     * Socket(InetAddress address, int port) throws UnknownHostException,IOException    参数一表示主机ip地址，参数二表示端口号
     * Socket(String host, int port) throws UnknownHostException,IOException            参数一表示主机名
     * Socket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
     *                                      第三、四参数用来设置客户端的ip地址和端口
     * Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException
     *
     *
     * InetAddress类
     * 表示主机ip地址，InetAddress类提供一系列静态工厂方法用于构造自身实例
     * InetAddress address = InetAddress.getLocalHost();    // 返回本机主机ip地址
     * InetAddress address = InetAddress.getByName("223.68.3.9");    // 返回代表“223.68.3.9”的IPv4地址
     * InetAddress address = InetAddress.getByName("2001:DB8:2DE::E13");    // 返回代表“2001:DB8:2DE::E13”的IPv6地址
     * InetAddress address = InetAddress.getByName("www.javathinker.net");    // 返回主机名为“www.javathinker.net”的ip地址
     * */
    public void test_2() {  // 同主机多ip地址
        try {
            InetAddress remoteAddress = InetAddress.getByName("112.5.4.45");
            InetAddress localAddress = InetAddress.getByName("112.5.4.3");
            Socket socket = new Socket(remoteAddress, 8000, localAddress, 2345);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void test_3() {  // 连接测试，客户端连接服务器时可能抛出的异常
        String host = "localhost";
        int port = 25;
        SocketAddress remoteAddress = new InetSocketAddress(host, port);
        Socket socket = null;
        String result = "";
        long begin = System.currentTimeMillis();        /* 获取当前时间，时间的表达格式为当前计算机时间和GMT时间
                                                        (格林威治时间)1970年1月1号0时0分0秒所差的毫秒数 */
        System.out.println(begin);
        socket = new Socket();
        try {
            socket.connect(remoteAddress, 1000);    // 超时时间为1秒
            long end = System.currentTimeMillis();
            result = (end - begin) + "ms";      // 计算连接所花的时间
        } catch (BindException e) {
            result = "Local address and port can't be binded";
        } catch (UnknownHostException e) {
            result = "Unknown host";
        } catch (ConnectException e) {
            result = "Connection Refused";
        } catch (SocketTimeoutException e) {
            result = "Timeout";
        } catch (IOException e) {
            result = "failure";
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println(remoteAddress + " : " + result);
    }

    /**
     * 代理服务器
     * Proxy
     * Proxy.Type       该类表示代理服务器的类型
     * 可选值：
     *      Proxy.Type.SOCKS    在分层网络结构中，Type.SOCKS是位于会话层的代理类型
     *      Proxy.Type.HTTP     在分层网络结构中，Type.HTTP是位于应用层的代理类型
     *      Proxy.Type.DIRECT   不使用代理，直接连接远程服务器
     * */
    public void test_4() {  // 使用代理服务器
        String proxyIP = "myproxy.abc.com";     // 代理服务器地址
        int proxyPort = 3030;   // 代理服务器端口
        Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyIP, proxyPort));   // 创建代理对象
        Socket socket = new Socket(proxy);
        try {
            socket.connect(new InetSocketAddress("www.javathinker.net", 80));   // 连接远程服务器
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Socket
     * 实现了java.lang.AutoClosable接口，
     * 在try代码块中打开或创建了Socket的实例，即使在程序中没有显示关闭，
     * java虚拟机也会在退出try代码块时自动关闭，释放相关资源
     *
     * 实例方法
     * getInetAddress()     获取远程被连接进程的ip地址
     * getPort()            获取远程被连接进程的端口
     * getLocalAddress()    获取本地的ip地址
     * getLocalPort()       获取本地的端口
     * getInputStream()     获取输入流，如果Socket未连接或已关闭或通过shutdownInput()发方法关闭输入流则会抛出IOException
     * getOutputStream()    获取输出流，如果Socket未连接或已关闭或通过shutdownOutput()发方法关闭输出流则会抛出IOException
     * Close()              关闭Socket，释放资源
     *
     * isClosed()           如果Socket未关闭则返回false，否则返回true
     * isConnected()        如果Socket曾连接到远程主机，不管当前是否已关闭都返回true，如果Socket从未连接到远程主机则返回false
     * isBound()            如果Socket已经与一个本地端口绑定则返回true，否则返回false
     *
     * 半关闭Socket的方法
     * shutdownInput()      关闭输入流
     * shutdownOutput()     关闭输出流
     * 半关闭方法不会关闭Socket对象释放资源，仍然需要调用Socket的close()方法
     *
     * public boolean isInputShutdown()     如果输入流关闭则返回true，否则返回false
     * public boolean isOutputShutdown()    如果输出流关闭则返回true，否则返回false
     *
     * sendUrgentData(int data)     发送1字节的TCP紧急数据
     * */
    public void test_5() {  // 访问网页
        String host = "www.javathinker.net";
        int port = 80;
        Socket socket = null;
        try {
            socket = new Socket(host, port);
            StringBuffer sBuf = new StringBuffer("GET " + "/index.jsp" + "http/1.1\r\n");
            sBuf.append("Host: ").append(host).append("\r\n");
            sBuf.append("Accept: */*\r\n");
            sBuf.append("Accept-Language: zh-cn\r\n");
            sBuf.append("Accept-Encoding: gzip, deflate\r\n");
            sBuf.append("User-Agent: HTTPClient\r\n");
            sBuf.append("Connection: Keep-Alive\r\n\r\n");

            // 发起HTTP请求
            OutputStream socketOut = socket.getOutputStream();
            socketOut.write(sBuf.toString().getBytes());
            socketOut.flush();

            // 接收响应结果
            InputStream socketIn = socket.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int len = -1;
            while((len = socketIn.read(buff)) != -1) {
                buffer.write(buff, 0, len);
            }
//            System.out.println(new String(buffer.toByteArray()));
            System.out.println(buffer.toString());
            /*
            * 显示乱码是因为www.javathinker.net服务器发送正文内容时将其压缩成为gzip格式，需要对压缩数据解压才能得到正文内容
            * */
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socket != null) {
                try {
                    socket.close();     // 关闭socket
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /*
     * ByteArrayOutputStream
     * 对byte类型数据进行写入的类，相当于一个中间缓冲层，将类写入到文件等其他outputStream
     * 它是对字节进行操作，属于内存操作流
     * ByteArrayOutputStream继承了OutputStream类
     *
     * 成员变量
     * protected byte buf[];     数据存储的地方
     * protected int count;      计数器  表示数据的个数
     *
     * 构造方法
     * //创建一个新的byte数组输出流。缓冲区的容量最初是32字节，如有必要可增加其大小
     * public ByteArrayOutputStream() {
     *      this(32);
     * }
     * //创建一个新的byte数组输出流，它具有指定大小的缓冲区容量（以字节为单位）
     * public ByteArrayOutputStream(int size) {
     *      if (size < 0) {
     *          throw new IllegalArgumentException("Negative initial size: " + size);
     *      }
     *      buf = new byte[size];
     * }
     *
     * 实例方法
     * //将指定的int类型的数据写入此byte数组输出流
     * public void write(int b){
     *      ensureCapacity(count + 1);
     *      buf[count] = (byte) b;
     *      count += 1;
     * }
     *
     * 将指定byte数组中从偏移量off开始的len个字节写入此byte数组输出流
     * public  void write(byte b[], int off, int len){
     *      if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) - b.length > 0)) {
     *          throw new IndexOutOfBoundsException();
     *      }
     *      ensureCapacity(count + len);
     *      System.arraycopy(b, off, buf, count, len);
     *      count += len;
     * }
     *
     * 将此byte数组输出流的全部内容写入到指定的输出流参数中
     * 这与使用out.write(buf, 0, count)调用该输出流的write方法效果一样
     * public synchronized void writeTo(OutputStream out) throws IOException {
     *      out.write(buf, 0, count);
     * }
     *
     * 将此byte数组输出流的count字段重置为零，从而丢弃输出流中目前已累积的所有输出
     * 通过重新使用已分配的缓冲区空间，可以再次使用该输出流
     * public synchronized void reset() {
     *      count = 0;
     * }
     *
     * 创建一个新分配的byte数组，其大小是此输出流的当前大小，并且缓冲区的有效内容已复制到该数组中
     * public synchronized byte toByteArray()[] {
     *      return Arrays.copyOf(buf, count);
     * }
     *
     * 返回缓冲区的当前大小
     * public synchronized int size() {
     *      return count;
     * }
     *
     * 使用平台默认的字符集，通过解码字节将缓冲区内容转换为字符串
     * 新String的长度是字符集的函数，因此可能不等于缓冲区的大小
     * 此方法总是使用平台默认字符集的默认替代字符串替代错误输入(malformed-input)和不可映射字符(unmappable-character)序列
     * 如果需要对解码过程进行更多控制，则应该使用CharsetDecoder类
     * public synchronized String toString() {
     *      return new String(buf, 0, count);
     * }
     *
     * 扩容方法：
     * private void ensureCapacity(int minCapacity) {
     *      // overflow-conscious code
     *      if (minCapacity - buf.length > 0)
     *          grow(minCapacity);
     * }
     *
     * grow方法：
     * private void grow(int minCapacity) {
     *      // overflow-conscious code
     *      int oldCapacity = buf.length;
     *      int newCapacity = oldCapacity << 1;
     *      if (newCapacity - minCapacity < 0)
     *          newCapacity = minCapacity;
     *      if (newCapacity - MAX_ARRAY_SIZE > 0)
     *          newCapacity = hugeCapacity(minCapacity);
     *      buf = Arrays.copyOf(buf, newCapacity);
     * }
     *
     *
     *
     * MAX_ARRAY_SIZE       为Integer最大值减8
     * private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
     * */

    public static void main(String[] args) {
        SocketDemo sd = new SocketDemo();
//        sd.test_1();
//        sd.test_3();
        sd.test_5();
    }

}
